#!/usr/local/bin/perl -w

# $Id$

=pod
=begin reST
=begin Id
Id: ${TOOL_ID}.
Copyright (C) 2002-2005 Freescale Semiconductor
Distributed under terms of the Perl license, which is the disjunction of
the GNU General Public License (GPL) and the Artistic License.
=end Id

=begin Description
Description of ${TOOL_NAME}
===========================
This program converts the DocUtils reStructuredText or
Document Object Model (DOM) (aka pseudo-XML) formats into an output
format.  The default output format is HTML, but different formats can
be specified by using different writer schemas.

=end Description
=begin Usage
Usage: ${TOOL_NAME} [options] file(s)

Options:
  -d             Print debugging info on STDERR.  May be used multiple
                 times to get more information.
  -e <encoding>  Specifies an encoding to use for I/O (default 'utf8')
  -h             Print full usage help
  -w <writer>    Process the writer schema from <writer>.wrt (default 'html')
  -D var[=val]   Define a variable that affects parsing (may be multiple)
  -W var[=val]   Define a variable that affects a writer (may be multiple)
  -V             Print version info

Available writers: ${\WriterList()}.
Available encodings: ${\EncodingList()}.
=end Usage
=end reST
=cut

# See comments in DOM.pm for DOM structure.
#
# Data structures:
#   _`Handler`: Hash reference with the following 
#     keys:
#       ``tag``:  Regular expression for tag matching
#       ``line``: Line number where function is defined
#       ``text``: Textual representation of the code to run on tag match
#       ``code``: Code reference for the code to run on tag match.
#                 The code is a subroutine with two arguments:
#
#                   the matching DOM object
#
#                   the string returned recursively from the content
#                   of this DOM.
#
#                 It needs to return a string.  Any string returned by the
#                 top level is printed to STDOUT.
#   _`Handler array`:    Reference to array of handler objects.

# Global variables:
#   ``$main::TOP_FILE``: Name of the top-level file being processed.
#   ``$main::MY_DIR``:   The real directory in which the prest script lives
#   ``$main::TOOL_ID``:  The tool name and release number
#   ``$main::VERSION``:  The prest version

use strict;

use vars qw($TOOL_NAME $YEAR $TOP_FILE $SVNID $SVNNAME $VERSION
	    $TOOL_ID);

main();

BEGIN {
    use Text::Restructured::PrestConfig;
    $SVNID = '$Id$ ';
    $SVNNAME = '$URL$ ';
    my $version = $Text::Restructured::PrestConfig::VERSION;
    $version =~ s/(\d\d\d)(?=\d)/$1./g;
    $version =~ s/(\d+)/$1+0/ge;
    $VERSION = $version;
    $SVNID =~ /Id: (\S+?) \S+ (\d+)/;
    $TOOL_ID = "$1 release $VERSION";
    $YEAR = $2;
    ($TOOL_NAME = $1) =~ s/\..*//;
}

# The main entry point.  Parses command-line options, preprocesses the
# writer schema, causes the document(s) to be read, and calls the writer.
sub main {
    use Getopt::Long;
    # Set default option values
    my %opt = (w=>'html', d=>0, D=>{}, e=>'utf8');

    # Parse options
    Getopt::Long::config('no_ignore_case');
    Usage() unless GetOptions \%opt, qw(d+ e:s h w=s D:s% W:s% V);
    # Give usage information
    Usage('Description') if $opt{h};
    Usage('Id') if $opt{V};
    Usage() unless @ARGV;

    # May need to specify an encoding for STDOUT
    if (($opt{e} || '') =~ /(.+)/) { # Sanitize for -T flag
	binmode STDOUT, ":encoding($1)" ;
    }

    use Text::Restructured::Writer;
    my $writer = new Text::Restructured::Writer($opt{w}, \%opt);

    use Text::Restructured::DOM;

    # Handle all the documents
    my $rst_parser;
    foreach $TOP_FILE (@ARGV) {
	# uncoverable branch true note:Bug in Devel::Cover
	open F,$TOP_FILE or die "Cannot open $TOP_FILE";
	if (($opt{e} || '') =~ /(.+)/) { # Sanitize for -T flag
	    binmode F, ":encoding($1)" ;
	}
 	my $dom;
	my $doc = do { local $/; <F> };
	if ($doc =~ /^<document/) {
	    # We have a DOM for input rather than an reST file
	    $dom = Text::Restructured::DOM::Parse($doc, \%opt);
	}
	else {
	    use Text::Restructured;
	    $rst_parser = new Text::Restructured(\%opt, $TOOL_ID)
		unless $rst_parser;
	    $dom = $rst_parser->Parse($doc, $TOP_FILE);
	}
	# Now compute the output string
	eval { print $writer->ProcessDOM($dom); };
	print STDERR $@ if $@;
    }
    $^W = 0;			# Turn off warnings; we're done
}

# Gets list of encodings
# Arguments: none
# Returns: list of writers
sub EncodingList {
    my @encodings;

    use Encode;
    @encodings = Encode->encodings(':all');
    return join(', ', @encodings);
}

# Gets list of writers
# Arguments: none
# Returns: list of writers
sub WriterList {
    my ($dir,@writers);
    my %writer_seen;
    foreach $dir (@INC) {
	push @writers, glob("$dir/Text/Restructured/Writer/*.wrt")
    }
    @writers = grep(! $writer_seen{$_}++,
		    grep(s|.*/([^/]+)\.wrt$|$1|, @writers));
    return join(', ', @writers);
}

# Extracts and prints usage information
# Arguments: type of usage, end marker for usage (optional)
sub Usage {
    my ($what) = @_;
    $what = "Usage" if ! $what;
    my $mark = $what eq 'Description' ? "($what|Usage)" : $what;
    # uncoverable branch false not:Assert I can open myself
    if (open(ME,$0)) {
	while (<ME>) {
	    if ((/^=begin $mark/ .. /^=end $mark/) &&
		! /^=(begin|end) $mark/) {
		s/(\$\{[^\}]+\})/eval($1)/ge;
		print;
	    }
	}
	close(ME);

	if ($what =~ /Description/) {
	    my @used = qw(Text/Restructured Text/Restructured/Transforms);
	    my %used;
	    @used{@used} = (1) x @used;
	    my $use;
	    foreach $use (@used) {
		my @rst_dir = grep (-r "$_/$use.pm", @INC);
		# uncoverable branch false note:Assert I can find my modules
		if (@rst_dir) {
		    my $newline_done;
		    my $file = "$rst_dir[0]/$use.pm";
		    # uncoverable branch true note:Assert I can open my modules
		    open(USE, $file) or die "Cannot open $file";
		    while (<USE>) {
			print "\n" unless $newline_done++;
			if ((/^=begin $mark/ .. /^=end $mark/) &&
			    ! /^=(begin|end) $mark/) {
			    s/(\$\{[^\}]+\}+)/eval $1/ge;
			    print;
			}
		    }
		    close USE;
		}
	    }
	    my (@directives, %directives);
	    my $dir;
	    foreach $dir (@INC) {
		grep(m|([^/]+)$| && ($directives{$1} = $_),
		     glob "$dir/Text/Restructured/Directive/*.pm");
	    }
	    @directives = map($directives{$_}, sort keys %directives);
	    # uncoverable branch false note:Assert I have directives
	    print << 'EOS' if @directives;

Descriptions of Plug-in Directives
==================================
EOS
	    foreach my $directive (@directives) {
		$directive =~ m|([^/]+)\.pm|;
		my $fname = $1;
		# uncoverable branch true note:Assert directive unique/readable
		next if $used{$fname} || ! -r $directive;
		my $output = 0;
		# uncoverable branch true note:Assert I can open directives
		open(DIRECTIVE, $directive) or die "Cannot open $directive";
		while (<DIRECTIVE>) {
		    if ((/^=begin $mark/ .. /^=end $mark/) &&
			    ! /^=(begin|end) $mark/) {
			if (! $output++) {
			    my $title = "Documentation for plug-in directive '$fname'";
			    print "\n$title\n",('-' x length($title)),"\n";
			}
			s/(\$\{[^\}]+\})/eval $1/ge;
			print;
		    }
		}
		close DIRECTIVE;
	    }

	    my @writers;
	    foreach $dir (@INC) {
		push(@writers, glob("$dir/Text/Restructured/Writer/*.wrt"));
	    }
	    my $writer;
	    # uncoverable branch false note:Assert I have writers
	    print << 'EOS' if @writers;

Descriptions of Writers
=======================
EOS
	;
	    my %done_writer;
	    foreach $writer (@writers) {
		my ($writer_name) = $writer =~ m|([^/]+)\.wrt$|;
		next if $done_writer{$writer_name}++;
		my $output = 0;
		# uncoverable branch true note:Assert I can open writers
		open(WRITER, $writer) or die "Cannot open $writer";
		while (<WRITER>) {
		    if ((/^=begin $mark/ .. /^=end $mark/) &&
			    ! /^=(begin|end) $mark/) {
			if (! $output++) {
			    my $title =
				"Documentation for writer '$writer_name'";
			    print "\n$title\n",('-' x length($title)),"\n";
			}
			s/(\$\{[^\}]+\})/eval $1/ge;
			print;
		    }
		}
		close WRITER;
	    }
	}
    }
    else {
	# uncoverable statement note:Defensive programming
	print STDERR "Usage not available.\n";
    }
    exit (1);
}
